# Go语言之http

http包提供了http客户端和服务端的实现,提供GetHeadPostPostForm函数发出http、https的请求。程序在使用完回复后必须关闭回复的主体。net/http源码学习

# 1 HTTP Server

Go中,实现一个最简单的http server非常容易,代码如下:

package main
import (
	"fmt"
	"net/http"
)

//访问http://127.0.0.1:9099/hello的回调处理函数
func IndexHandlers(w http.ResponseWriter, r *http.Request){
    //w.Write([]byte("hello world!"))
	fmt.Fprintln(w, "hello, world")
}

func main (){
	//HandleFunc注册一个处理器函数handler和对应的模式pattern。
	http.HandleFunc("/hello", IndexHandlers)
	//ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。
	// handler参数一般会设为nil,此时会使用DefaultServeMux。
	err := http.ListenAndServe("127.0.0.1:9099", nil)
	//如果监听失败了,就打印报错信息
	if err != nil {
		fmt.Printf("listen error:[%v]", err.Error())
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

httphello

# 2 http.Get和 http.Post

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

func main() {
	//模拟一个post提交请求
    //  (resp *Response): 网络服务返回的响应内容的结构化表示
    // (err error): 创建和发送HTTP请求,以及接收和解析HTTP响应的过程中可能发生的错误
	resp, err := http.Post("http://www.baidu.com", "application/x-www-form-urlencoded", strings.NewReader("id=1"))
	if err != nil {
		panic(err)
	}
	//关闭连接
	defer resp.Body.Close()
	//读取报文中所有内容
	body, err := ioutil.ReadAll(resp.Body)
	//输出内容
	fmt.Println(string(body))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 3 http.Client类型

http.Client是一个结构体,并且它包含的字段都是公开的。该类型是开箱即用的,因为它的所有字段,要么存在相应的缺省值,要么其零值直接就可以使用,并且代表着特定的含义。

type Client struct {
    Transport RoundTripper
    CheckRedirect func(req *Request, via []*Request) error
    Jar CookieJar
    Timeout time.Duration
}
1
2
3
4
5
6

# 3.1 Transport字段

主要看下Transport字段,该字段向网络服务发送HTTP请求,并从网络服务接收HTTP响应。该字段的方法RoundTrip应该实现单次HTTP事务(或者说基于HTTP协议的单次交互)需要的所有步骤。这个字段是一个接口:

type RoundTripper interface {
    RoundTrip(*Request) (*Response, error)
}
1
2
3

并且该字段有一个由http.DefaultTransport变量的缺省值:在初始化http.Client类型的时候,如果没有显式的为该字段赋值,这个Client字段就会直接使用DefaultTransport

func (c *Client) transport() RoundTripper {
    if c.Transport != nil {
        return c.Transport
    }
    return DefaultTransport
}
1
2
3
4
5
6

# 3.2 Timeout字段

该字段是单次HTTP事务的超时时间,它是time.Duration类型。它的零值是可用的,用于表示没有设置超时时间。

# 3.3 http.Transport类型

http.Transport类型是一个结构体,该类型包含的字段很多。这里通过http.Client结构体中的Transport字段的缺省值DefaultTransport,来深入了解一下。DefaultTransport是一个*http.Transport的结构体,做了一些默认的设置:

var DefaultTransport RoundTripper = &Transport{
    Proxy: ProxyFromEnvironment,
    DialContext: (&net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
        DualStack: true,
    }).DialContext,
    MaxIdleConns:          100,
    IdleConnTimeout:       90 * time.Second,
    TLSHandshakeTimeout:   10 * time.Second,
    ExpectContinueTimeout: 1 * time.Second,
}
1
2
3
4
5
6
7
8
9
10
11
12

这里Transport结构体的指针就是就是RoundTripper接口的默认实现:

func (t *Transport) RoundTrip(req *Request) (*Response, error) {
    return t.roundTrip(req)
}
1
2
3

这个类型是可以被复用的,并且也推荐被复用。同时它也是并发安全的。所以http.Client类型也是一样,推荐复用,并且并发安全。 看上面的默认设置,http.Transport类型,内部的DialContext字段会使用net.Dialer类型的值,并且把Timeout设置为30秒。仔细看,该值是一个方法,这里把Dialer值的DialContext方法赋值给了DefaultTransport里的同名字段,并且已经设置好了调用该方法时的结构体。\

# 4 Server示例

package main

import (
	"fmt"
	"net/http"
	"os"
	"sync"
)

var wg sync.WaitGroup

// 一般没有这么用的,http.Server的Handler字段
// 要么是nil,就用包里的http.DefaultServeMux
// 要么用NewServeMux()来创建一个*http.ServeMux
// 我这里按照http.Handler接口的要求实现了一个,赋值给Handler字段
// 这个自定义的Handler不支持路由
func startServer1() {
	defer wg.Done()
	var httpServer http.Server
	httpServer.Addr = "127.0.0.1:8001"
	httpServer.Handler = http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			fmt.Println(*r)
			fmt.Fprint(w, "Hello World")
		},
	)
	fmt.Println("启动服务,浏览器访问: http://127.0.0.1:8001")
	if err := httpServer.ListenAndServe(); err != nil {
		if err == http.ErrServerClosed {
			fmt.Println("HTTP Server1 Closed.")
		} else {
			fmt.Fprintf(os.Stderr, "HTTP Server1 Error: %v\n", err)
		}
	}
}

// 这个最简单,都是调用http包里的函数。本质上还是要调用方法的,都会用默认的或是零值
func startServer2() {
	defer wg.Done()
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello World\nThis is Server2")
	})
	fmt.Println("启动服务,浏览器访问: http://127.0.0.1:8002")
	// 第二个参数传nil,就是用包里的http.DefaultServeMux,或者也可以自己创建一个传给第二个参数
	if err := http.ListenAndServe("127.0.0.1:8002", nil); err != nil {
		if err == http.ErrServerClosed {
			fmt.Println("HTTP Server2 Closed.")
		} else {
			fmt.Fprintf(os.Stderr, "HTTP Server2 Error: %v\n", err)
		}
	}
}

// 这个例子里用到了解析Get请求的参数,并且还设置了2个路由
func startServer3() {
	defer wg.Done()
	mux := http.NewServeMux()
	mux.HandleFunc("/hi", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/hi" {
			// 这个分支应该是进不来的,因为要进入这个分支,路径应该必须是"/hi"
			fmt.Println("Server3 hi 404")
			http.NotFound(w, r)
			return
		}
		name := r.FormValue("name")
		if name == "" {
			fmt.Fprint(w, "Hi!")
		} else {
			fmt.Fprintf(w, "Hi, %s!", name)
		}
	})
	mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello World\nThis is Server3")
	})
	// 如果只是定义http.Server的下面2个字段,完全可以使用http.ListenAndServe函数来启动服务
	// 这样的用法可以对http.Server里更多的字段进行自定义
	httpServer := http.Server{
		Addr: "127.0.0.1:8003",
		Handler: mux,
	}
	fmt.Println("启动服务,浏览器访问: http://127.0.0.1:8003/hi?name=Adam")
	if err := httpServer.ListenAndServe(); err != nil {
		if err == http.ErrServerClosed {
			fmt.Println("HTTP Server3 Closed.")
		} else {
			fmt.Fprintf(os.Stderr, "HTTP Server3 Error: %v\n", err)
		}
	}
}

func main() {
	wg.Add(1)
	go startServer1()
	wg.Add(1)
	go startServer2()
	wg.Add(1)
	go startServer3()
	wg.Wait()
}
--------------------------输出结果-------------------------------
启动服务,浏览器访问: http://127.0.0.1:8003/hi?name=Adam
启动服务,浏览器访问: http://127.0.0.1:8002
启动服务,浏览器访问: http://127.0.0.1:8001
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

# 5 优雅的停止HTTP服务

包里还提供了一个Shutdown方法,可以优雅的停止HTTP服务:

func (srv *Server) Shutdown(ctx context.Context) error {
    // 内容省略
}
1
2
3

我们要做的就是在需要的时候,可以调用该Shutdown方法。 这里的问题是,调用了ListenAndServe方法之后,就进入了无限循环的流程。 这里最好是用一个goroutine来启动ListenAndServe方法,在goroutine外声明http.Server。 然后在主线程里等待一个信号,比如是从通道接收值。这样就可以在主线程里调用这个Shutdown方法执行了。

# 6 更多参考

请参考Go语言标准库中文文档